#include "pwext.hpp"
#include <list>
#include <stdio.h>
#include <unistd.h>

// [[ex03cd]]
// DEBUG descr Fine-grained locking of the complex data structures.
struct fgelement {
	pw::hold hold_; // used to prevent deletion
	pw::pmutex mutex_; // used for access to the payload
	int payload_; 
};

class fglist 
{
public:
	typedef std::list <fgelement *> listdata;
	listdata data_;
	// used to synchronize the access to the list
	pw::pmutex mutex_; 

	typedef bool bool_callback_t(void *token, 
		fgelement *);
	typedef void void_callback_t(void *token, 
		fgelement *);

	void add_element(fgelement *el)
	{
		pw::lockmutex lm(mutex_);
		data_.push_back(el);
	}

	// returns true if successfully deleted, false if no
	// such element
	bool delete_element(fgelement *el)
	{
		{
			pw::lockmutex lm(mutex_);
			listdata::iterator it = data_.begin();
			for (;;++it) {
				if (it == data_.end())
					return false;
				if (*it == el)
					break;
			}
			data_.erase(it);
		}
		// after this point nobody else can delete this
		// element
		el->hold_.wait();
		delete el;
		return true;
	}

	// do some processing on the element, returns
	// true on success or false if no such element;
	// find_cb selects which element(s) to process;
	// process_cb does the processing;
	// token is the token passed to the callbacks
	bool process(bool_callback_t *find_cb, 
		void_callback_t *process_cb, void *token)
	{
		fgelement *el;
		{
			pw::lockmutex lm(mutex_);
			listdata::iterator it = data_.begin();
			for (;;++it) {
				if (it == data_.end())
					return false;
				if (find_cb(token, *it))
					break;
			}
			el = *it;
			el->hold_.acquire();
		}
		// while the hold is held, the element won't be
		// deleted
		{
			// locking this mutex may take a while but the
			// whole lists's mutex is released by now
			pw::lockmutex lm(el->mutex_);
			process_cb(token, el);
		}
		el->hold_.release(); // done with the element
		return true;
	}
};
// END

// DEBUG {

fglist lst;
pw::semaphore ready(0);
pw::event startev;

// start() can be used only in the innermost thread class constructor,
// otherwise it gets executed with a half-constructed object and
// things go haywired

class inserter : public pw::pwthread
{
public:
	inserter(int nstart) :
		 nstart_(nstart) 
	{ 
		start();
	}
	~inserter()
	{
		join();
	}

	void *execute()
	{
		ready.signal(1);
		startev.wait();
		for(int i = 0; i < 3; i++) {
			usleep(1);
			fgelement *el = new fgelement;
			el->payload_ = nstart_ + i;
			lst.add_element(el);
			fprintf(stderr, "thread %p added %p %d\n", this, el, nstart_ + i);
		}
		return 0;
	}
	int nstart_;
};

class deleter : public pw::pwthread
{
public:
	deleter()
	{ 
		start();
	}
	~deleter()
	{
		join();
	}

	void *execute()
	{
		ready.signal(1);
		startev.wait();
		while(true) {
			fgelement *el;
			{
				pw::lockmutex lm(lst.mutex_);
				if (lst.data_.empty())
					return 0;
				el = lst.data_.front();
			}
			usleep(1);
			fprintf(stderr, "thread %p delete %p: %s \n", this, el, 
				lst.delete_element(el)? "success":"failure");
		}
		return 0;
	}
};

class processor : public pw::pwthread
{
public:
	processor()
	{ 
		start();
	}
	~processor()
	{
		join();
	}

	void *execute()
	{
		fprintf(stderr, "process %s \n", 
			lst.process(findcb, processcb, 0)? "success":"failure");
		return 0;
	}

	static bool findcb(void *token, fgelement *el)
	{
		return (el->payload_ == 11);
	}
	static void processcb(void *token, fgelement *el)
	{
		fprintf(stderr, "holding on to element %p\n", el);
		startev.wait();
		usleep(300*1000);
		fprintf(stderr, "releasing element %p\n", el);
	}
};

int main()
{
	startev.reset();
	{
		inserter i1(10); 
		inserter i2(20);
		ready.wait(2);
		startev.signal();
	}
	startev.reset();
	processor pr;
	{
		deleter d1, d2;
		ready.wait(2);
		startev.signal();
	}
	startev.reset();
	return 0;
}
// DEBUG }
/* Sample output:

thread 0x7fffdd7bc9c0 added 0x7f48d40008c0 10
thread 0x7fffdd7bc9c0 added 0x7f48d4000980 11
thread 0x7fffdd7bc970 added 0x7f48cc0008c0 20
thread 0x7fffdd7bc9c0 added 0x7f48d4000a40 12
thread 0x7fffdd7bc970 added 0x7f48cc000980 21
thread 0x7fffdd7bc970 added 0x7f48cc000a40 22
holding on to element 0x7f48d4000980
thread 0x7fffdd7bca60 delete 0x7f48d40008c0: success 
thread 0x7fffdd7bca10 delete 0x7f48d40008c0: failure 
thread 0x7fffdd7bca10 delete 0x7f48cc0008c0: success 
thread 0x7fffdd7bca10 delete 0x7f48d4000a40: success 
thread 0x7fffdd7bca10 delete 0x7f48cc000980: success 
thread 0x7fffdd7bca10 delete 0x7f48cc000a40: success 
releasing element 0x7f48d4000980
process success 
thread 0x7fffdd7bca60 delete 0x7f48d4000980: success 

*/
